<?php
/*******************************************************************************
Rd/Array.php
Array utility class

Created by Troy Hurteau, NCSU Libraries, NC State University (libraries.opensource@ncsu.edu).

This file is part of NCSU's distribution of ReservesDirect. This version has not been downloaded from Emory University
or the original developers of ReservesDirect. Neither Emory University nor the original developers of ReservesDirect have authorized
or otherwise endorsed or approved this distribution of the software.

Copyright (c) 2004-2006 Emory University, Atlanta, Georgia.

Licensed under the NCSU ReservesDirect License, Version 2.0 (the "License"); 
you may not use this file except in compliance with the License. You may obtain a copy of the full License at
 http://www.lib.ncsu.edu/it/opensource/

ReservesDirect is distributed in the hope that it will be useful,
but is distributed "AS IS" and WITHOUT ANY WARRANTY, without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE, and without any warranty as to non-infringement of any third
party's rights. See the License for the specific language governing permissions and limitations under the License.

The original version of ReservesDirect is located at:
http://www.reservesdirect.org/

This version of ReservesDirect, distributed by NCSU, is located at:
http://code.google.com/p/reservesdirect-ncsu/

*******************************************************************************/

require_once(APPLICATION_PATH . '/lib/Rd/Exception/NotAnArray.php');
require_once(APPLICATION_PATH . '/lib/Rd/Exception/NoDefault.php');

/**
 * Utility functions for Arrays
 */
class Rd_Array {
	
	const TYPE_NULL = 1;
	const TYPE_STRING = 2;
	const TYPE_ARRAY = 4;
	
	const MATCH_EXACT = 0;
	const MATCH_EQUAL = 1;
	const MATCH_LOOSE = 2;
	
	/**
	 * searches the passed array for the specified key. If
	 * it does not exist, return the $default value or
	 * throw an exception if no default is given. The default
	 * value may not be null.
	 * 
	 * @param mixed $key key in the array to check
	 * @param array $array array to search
	 * @param default $default value if key is not in array
	 */
	public static function extract($key, $array, $default = NULL)
	{
		if(!is_array($array) && (!is_object($array) && !method_exists($array,'__toArray'))){
			throw new Rd_Exception_NotAnArray();
		}
		if(NULL === $default && !array_key_exists($key, $array)){
			throw new Rd_Exception_NoDefault();
		}
		return(
			array_key_exists($key, $array) 
			? $array[$key]
			: $default
		);
	}

	/**
	 * searches the passed array for the specified key. If
	 * it does not exist, return NULL
	 * 
	 * @param mixed $key key in the array to check
	 * @param array $array array to search
	 */
	public static function extractOptional($key, $array)
	{
		try {
			return self::extract($key,$array);
		} catch (Rd_Exception_NoDefault $e) {
			return NULL;
		}
	}
	
	/**
	 * searches the passed array for the specified key. If
	 * it does not exist, return the $default value or
	 * throw an exception if no default is given. The default
	 * value may not be null.
	 * 
	 * @param mixed $key key in the array to check
	 * @param array $array array to search
	 * @param default $default value if key is not in array
	 */
	public static function extractIfArray($key, $array, $default = NULL)
	{
		return(
			is_array($array) && array_key_exists($key, $array)
			? $array[$key]
			: $default
		);
	}
	
	/**
	 * takes an array of string keyed arrays, and flattens the values out by
	 * adding '1' to the end of the keys of the first array, '2' to the end
	 * of keys from the second and so forth.
	 * 
	 * @param array $array to flatten
	 * @return array flattened data
	 */
	public static function flatten($array)
	{
		$i = 1;
		$flattenedArray = array();
		foreach ($array as $innerArray){
			foreach ($innerArray as $originalKey=>$innerData){
				$flattenedArray[$originalKey . $i] = $innerData; 
			}
			$i++;
		}
		return $flattenedArray;
	}
	
	/**
	 * takes string or array of strings and array, returning an array where
	 * no string in the first parameter appears as a key in the new array.
	 * 
	 * @param mixed $exclude string or array of strings to exclude
	 * @param array $array from which some keys may be excluded
	 * @return array subset of $array
	 */
	public static function excludeKeys($exclude, $array)
	{
		if (!is_array($exclude)){
			$exclude = array($exclude);
		}
		foreach ($exclude as $key){
			unset($array[$key]);
		}
		return $array;
	}
	
	/**
	 * Serializes an array into a string using the formatting of print_r()
	 * 
	 * @param array $array to serialize
	 * @return string representation of $array
	 */
	public static function toString($array)
	{
		ob_start();
		print_r($array);
		$return = ob_get_contents();
		ob_end_clean();
		return $return;
	}
	
	public static function toHtml($array, $ordered = FALSE, $nested = TRUE)
	{
		$return = '';
		$return .= ($ordered ? '<ol>' : '<ul>');
		if (count($array) == 0) {
			$return .= '<li>None</li>';
		} else {
			foreach($array as $key=>$value){
				$return .= '<li>';
				if (is_object($value)) {
					$value = Rd_Debug::introspectData($value); //#TODO make a more user friendly version of this output
				}
				if ($nested && is_array($value)) {
					$value = self::toHtml($value, $ordered);
				}
				$return .= "{$key}: {$value}";
				$return .= '</li>';
			}
		}
		$return .= ($ordered ? '</ol>' : '</ul>'); 
		return $return;
	}
	
	/**
	 * Searches for string $key in array $array and returns true only if
	 * it the key exists and the value it contains is an integer, boolean, or
	 * non-empty. Optional third parameter can allow one or more falsy values:
	 * NULL : Rd_Array::TYPE_NULL, 
	 * empty array : Rd_Array::TYPE_ARRAY, 
	 * empty string : Rd_Array::TYPE_STRING
	 * 
	 * $key may be an array, this method will return true if all are present.
	 * 
	 * @param string $key string array key to search for
	 * @param array $array to be searched
	 * @param int $allowedBlankTypes bitwise integer of blank types that are allowed
	 * @return bool key exists and value is not blank
	 */	
	public static function keyExistsAndNotBlank($key, $array, $allowedBlankTypes = 0)
	{
		if (!is_array($array)) {
			Rd_Debug::out('Rd_Array::keyExistsAndNotBlank got a non-array operand.');
			return false;
		}
		if (!is_array($key)) {
			$key = array($key);
		}
		foreach ($key as $arrayKey) {
			if(
				!array_key_exists($arrayKey, $array)
				|| !( $allowedBlankTypes & self::TYPE_NULL || !is_null($array[$arrayKey]))
				|| !(
					is_object($array[$arrayKey])
					|| (
						is_array($array[$arrayKey]) 
						&& ($allowedBlankTypes & self::TYPE_ARRAY || count($array[$arrayKey]) > 0)
					) || (is_string($array[$arrayKey])
						&& ($allowedBlankTypes & self::TYPE_STRING || '' != trim($array[$arrayKey]))
					) || is_bool($array[$arrayKey])
					|| (is_numeric($array[$arrayKey]) && !is_string($array[$arrayKey]))
					|| is_resource($array[$arrayKey])
				)
			) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Searches for string $key in array $array and returns true only if
	 * it the key exists and the value matches $value. The optional fourth 
	 * parameter determines how the match is determined, defaults to == :
	 * == : Rd_Array::MATCH_EQUAL, 
	 * === : Rd_Array::MATCH_EXACT, 
	 * string, case insensitive, trimmed : Rd_Array::MATCH_LOOSE
	 * 
	 * $key may be an array, this method will return true if all are present.
	 * 
	 * @param string $key string array key to search for
	 * @param array $array to be searched
	 * @param int $allowedBlankTypes bitwise integer of blank types that are allowed
	 * @return bool key exists and value is not blank
	 */	
	public static function keyExistsAndEquals($key, $array, $value, $matchType = Rd_Array::MATCH_EQUAL)
	{
		if (!is_array($key)) {
			$key = array($key);
		}
		foreach ($key as $arrayKey) {
			if( ! array_key_exists($arrayKey, $array)) {
				return false;
			} else {
				switch ($matchType) {
					case Rd_Array::MATCH_EXACT:
						if ($value !== $array[$arrayKey]) {
							return false;
						}
						break;
					case Rd_Array::MATCH_LOOSE:
						if(is_string($value)) {
							$value = strtolower(trim($value));
							$array[$arrayKey] = strtolower(trim($array[$arrayKey]));
						}
					case  Rd_Array::MATCH_EQUAL:
					default:
						if ($value != $array[$arrayKey]) {
							return false;
						}
				}
			}
		}
		return true;
	}
	
}